#include "stdafx.h"
#include "MML.h"

#include "Exception.h"

namespace Cvekas {

MMLNode::MMLNode(const std::string& name, const std::string& value)
: name(name), value(value) 
{
}

MMLNode::MMLNode(const MMLNode& node)
: children(node.children), name(node.name), value(node.value)
{
}

MMLNode& MMLNode::getChild(const std::string& name)
{
	std::list<MMLNode>::iterator itr = find(name); 

	if(itr == children.end())
		throw EXCEPTION("Child with name" + name + " not found!");
	
	return *itr;
}

void MMLNode::removeChild(const std::string& name)
{
	std::list<MMLNode>::iterator itr = find(name); 
	
	if(itr == children.end())
		throw EXCEPTION("Child with name " + name + " not found!");
	
	children.erase(itr);
}

void MMLNode::insertAfter(const std::string& name, const MMLNode& node)
{
	std::list<MMLNode>::iterator itr = find(name); 
	
	if(itr == children.end())
		throw EXCEPTION("Child with name " + name + " not found!");

	children.insert(++itr, node);
}

void MMLNode::append(const MMLNode& node)
{
	children.push_back(node);
}

std::list<MMLNode>::iterator MMLNode::find(const std::string& name)
{
	std::list<MMLNode>::iterator itr = children.begin();
	for(; itr != children.end(); ++itr)
	{
		if(itr->name == name)
			break;
	}
	return itr;
}


MML::MML()
: root("root")
{
}

MML::MML(const std::string& input)
: root("")
{
	std::vector<MMLToken> tokens;

	tokenize(input, tokens);
	root = parse(tokens);
}

MMLNode& MML::getRoot()
{
	return root;
}

std::string MML::serialize()
{
	return _serialize(root);
}

// Tokenizer helpers
bool is_whitespace(char c)
{
	return (c == ' ' || c == '\t' || c == '\n');
}

std::string filter_escape(const std::string& in)
{
	std::string result;
	// Check all but last chars
	for(size_t i = 0; i < in.size(); ++i)
	{
		if(in[i] != '\\')
		{
			result += in[i];
			continue;
		}	

		switch(in[++i])
		{
			case 'n':
				result += '\n';
				break;
			case 'r':
				result += '\r';
				break;
			case 't':
				result += '\t';
				break;
			case 'b':
				result += '\b';
				break;
			default:
				result += in[i];
		}
	}

	return result;
}

char escape(char in)
{
	switch(in)
	{
		case '\n':
			return 'n';
		case '\r':
			return 'r';
		case '\t':
			return 't';
		case '\b':
			return 'b';
		default:
			return in;
	}
}

std::string insert_escape(const std::string& in)
{
	std::string escape_chars(" ()\"\\\n\r\t\b");
	std::string result = "";

	for(size_t i = 0; i < in.size(); ++i)
	{
		bool escaped = false;
		for(size_t j = 0; j < escape_chars.size(); ++j)
		{
			if(in[i] == escape_chars[j])
			{
				result += '\\';
				result += escape(escape_chars[j]);
				escaped = true;
				break;
			}	
		}
		if(!escaped)
			result += in[i];
	}
	return result;
}

bool needs_escaping(const std::string& str)
{
	std::string escape_chars(" ()\"\\\n\r\t\b");
	for(size_t i = 0; i < escape_chars.size(); ++i)
	{
		if(str.find(escape_chars[i]) != std::string::npos)
			return true;
	}
	return false;
}

void MML::tokenize(const std::string& input, std::vector<MMLToken>& tokens)
{
	// Tokenizer state
	bool in_literal = false;
	bool in_qliteral = false;
	bool in_comment = false;
	size_t literal_start = 0;

	// Iterate through every char
	for(size_t i = 0; i < input.size(); ++i)
	{
		// Comment, skip all chars till newline
		if(in_comment)
		{
			if(input[i] == '\n')
				in_comment = false;
			continue;	
		}
		
		// Handle quoted literal
		if(in_qliteral)
		{
			if(input[i] == '"' && input[i-1] != '\\')
			{
				in_qliteral = false;
				std::string lit = input.substr(literal_start, i-literal_start); 
				tokens.push_back(MMLToken(MMLToken::MML_LITERAL,
					filter_escape(lit)));
			}
			continue;
		}

		// Comment start
		if(input[i] == '#')
		{
			in_comment = true;
			continue;
		}

		// Quoted literal start
		if(input[i] == '"')
		{
			in_qliteral = true;
			literal_start = i+1;
			continue;
		}

		// Whitespace, end literal
		if(is_whitespace(input[i]))
		{
			if(in_literal)
			{
				in_literal = false;
				tokens.push_back(MMLToken(MMLToken::MML_LITERAL,
					input.substr(literal_start, i-literal_start)));
			}
			continue;
		}

		// Braces
		if(input[i] == '(')
		{
			if(in_literal)
			{
				in_literal = false;
				tokens.push_back(MMLToken(MMLToken::MML_LITERAL,
					input.substr(literal_start, i-literal_start)));
			}

			tokens.push_back(MMLToken(MMLToken::MML_BRACE_OPEN));
			continue;
		}
		if(input[i] == ')')
		{
			if(in_literal)
			{
				in_literal = false;
				tokens.push_back(MMLToken(MMLToken::MML_LITERAL,
					input.substr(literal_start, i-literal_start)));
			}

			tokens.push_back(MMLToken(MMLToken::MML_BRACE_CLOSE));
			continue;
		}

		// Different char - start new literal
		if(!in_literal)
		{
			in_literal = true;
			literal_start = i;
		}
	}

	if(in_literal)
		throw EXCEPTION("Unexpected literal in the end");
	if(in_qliteral)
		throw EXCEPTION("Quoted literal not finished");
	if(in_comment)
		throw EXCEPTION("No newline after comment");
}

MMLNode MML::parse(const std::vector<MMLToken>& tokens)
{
	return _parse(tokens, 0, tokens.size());	
}

MMLNode MML::_parse(const std::vector<MMLToken>& tokens, size_t start, size_t end)
{
	size_t size = end - start;

	// Shortest node has 4 tokens, ex. ( name val )
	if(size < 4)
		throw EXCEPTION("Node must containt at least 4 tokenss");

	// Check if first & last tokens are braces	
	if(tokens[start].type != MMLToken::MML_BRACE_OPEN || 
		tokens[end-1].type != MMLToken::MML_BRACE_CLOSE)
		throw EXCEPTION("Node must begin and end with proper braces");

	// Check if second and third tokens are literals	
	if(tokens[start+1].type != MMLToken::MML_LITERAL || 
		tokens[start+2].type != MMLToken::MML_LITERAL)
		throw EXCEPTION("Node must have name and value literals");

	MMLNode result(tokens[start+1].literal, tokens[start+2].literal);	

	// Iterate through all literals between value and closing brace
	for(size_t i = start+3; i < end-1; ++i)
	{
		if(tokens[i].type == MMLToken::MML_LITERAL)
			throw EXCEPTION("Unexpected literal");
		if(tokens[i].type == MMLToken::MML_BRACE_CLOSE)
			throw EXCEPTION("Unexpected closing brace");

		// Children found...
		if(tokens[i].type == MMLToken::MML_BRACE_OPEN)
		{
			int depth = 1;

			// Determine length and recursively parse it
			for(size_t j = i+1; j < end-1; ++j)
			{
				if(tokens[j].type == MMLToken::MML_BRACE_OPEN)
					depth++;
				if(tokens[j].type == MMLToken::MML_BRACE_CLOSE)
					depth--;

				if(depth == 0)
				{
					result.children.push_back(_parse(tokens, i, j+1));
					i = j;
					break;
				}	
			}

			if(depth != 0)
				throw EXCEPTION("Misplaced brace");
		}		
	}

	return result;
}

std::string process_literal(const std::string& literal)
{
	if(needs_escaping(literal))
		return "\"" + insert_escape(literal) + "\"";
	return literal;	
}

std::string MML::_serialize(const MMLNode& node, const std::string& prefix)
{
	std::string result = prefix + "( " + process_literal(node.name) 
		+ " " + process_literal(node.value); 

	// Nodes with no children occupy single line
	if(node.children.size() == 0)
		return result + " )\n";
	result += "\n";

	// Recursively serialize all children
	std::list<MMLNode>::const_iterator itr = node.children.begin();
	for(; itr != node.children.end(); ++itr)
	{
		result += _serialize(*itr, prefix + "    ");	
	}

	return result + prefix + ")\n";
}

} // namespace

